package spring.parsing;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Element;
import org.dom4j.Node;
import spring.aop.Advice;
import spring.aop.AopConfig;
import spring.aop.AopPointcut;
import spring.aop.TxAdviceRegistry;
import spring.exceptions.AopParseException;
import spring.factory.SingletonBeanRegistry;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class XmlAopParser extends AbstractXmlParser {

    public XmlAopParser(SingletonBeanRegistry singletonBeanRegistry) {
        super(singletonBeanRegistry);
    }

    public TxAdviceRegistry getTxAdviceRegistry() {
        return (TxAdviceRegistry) singletonBeanRegistry;
    }

    @Override
    public XmlNodeType getType() {
        return XmlNodeType.AOP;
    }

    @Override
    public void parse(Element element) {
        String id = AopConfig.class.getSimpleName();
        if (singletonBeanRegistry.containsSingleton(id)) {
            return;
        }
        Map<String, AopPointcut> pointcutMap = new HashMap<>(10);
        AopConfig aopConfig = new AopConfig();
        List<Node> pointcuts = element.selectNodes("//aop-pointcut");
        for (Node node : pointcuts) {
            Element pointcutEl = (Element) node;
            AopPointcut pointcut = new AopPointcut();
            pointcut.setId(pointcutEl.attributeValue("id"));
            pointcut.setExpression(replaceBooleanOperators(pointcutEl.attributeValue("expression")));
            pointcutMap.put(pointcut.getId(), pointcut);
        }
        pointcuts = element.selectNodes("//aop-advisor");
        for (Node node : pointcuts) {
            Element advisor = (Element) node;
            String adviceRef = advisor.attributeValue("advice-ref");
            String pointcutRef = advisor.attributeValue("pointcut-ref");
            Advice advice = getTxAdviceRegistry().getAdvice(adviceRef);
            AopPointcut pointcut = pointcutMap.get(pointcutRef);
            if (Objects.isNull(pointcut)) {
                throw new AopParseException("pointcut-ref=\"" + pointcutRef + "\" 配置错误，找不到有效的方法切点。");
            }
            if (Objects.isNull(advice)) {
                aopConfig.addAdvisorFactory(adviceRef, pointcut);
            } else {
                aopConfig.addAdvisor(advice, pointcut);
            }
        }
        getTxAdviceRegistry().registerAopConfig(aopConfig);
    }

    private String replaceBooleanOperators(String pcExpr) {
        String result = StringUtils.replace(pcExpr, " and ", " && ");
        result = StringUtils.replace(result, " or ", " || ");
        result = StringUtils.replace(result, " not ", " ! ");
        return result;
    }

}
